import {
  useMemo,
  useRef,
} from 'react'
import { isFunction } from '@/library/tool-kits/is-valid'
import { error } from '@/library/tool-kits/log'

type noop = (this: any, ...args: any[]) => any;

type PickFunction<T extends noop> = (
  this: ThisParameterType<T>,
  ...args: Parameters<T>
) => ReturnType<T>;

/**
 * @description 持久化 function的hook，一般情况下，可以使用 useMemoizedFn 完全代替 useCallback
 * @description 在某些场景中，我们需要使用 useCallback 来记住一个函数，但是在第二个参数 deps 变化时，会重新生成函数，导致函数地址变化
 * @description 使用 useMemoizedFn，可以省略第二个参数 deps，同时保证函数地址永远不会变化
 * @description 使用 useMemoizedFn 不会遇到函数闭包问题
 */
export const useMemoizedFn = <T extends noop>(fn: T): T => {
  if (!isFunction(fn)) {
    error(
      'hooks',
      `useMemoizedFn expected parameter is a function, got ${typeof fn}`,
    )
  }

  const fnRef = useRef<T>(fn)

  fnRef.current = useMemo(() => fn, [fn])

  const memoizedFn = useRef<PickFunction<T>>()

  if (!memoizedFn.current) {
    memoizedFn.current = function (this, ...args) {
      return fnRef.current.apply(this, args)
    }
  }

  return memoizedFn.current as T
}
